#include <core.p4>
#include <v1model.p4>

#include "includes/headers.p4"
#include "includes/metadata.p4"
#include "includes/parser.p4"
#include "includes/macros.p4"

#include "pipeline/flowselector.p4"


control ingress(inout Parsed_packet pp,
                inout custom_metadata_t custom_metadata,
                inout standard_metadata_t standard_metadata) {

    /** Registers used by the Flow Selector **/
    register<bit<32>>(MAX_NB_PREFIXES*FLOWSELECTOR_NBFLOWS) flowselector_key;             //定义了一个 32 位宽的寄存器，用于存储流选择器的键。
    register<bit<32>>(MAX_NB_PREFIXES*FLOWSELECTOR_NBFLOWS) flowselector_nep;             //定义了一个 32 位宽的寄存器，用于存储流选择器的下一跳端口
    register<bit<9>>(MAX_NB_PREFIXES*FLOWSELECTOR_NBFLOWS) flowselector_ts;               //定义了一个 9 位宽的寄存器，用于存储流选择器的时间戳。
    register<bit<19>>(MAX_NB_PREFIXES*FLOWSELECTOR_NBFLOWS) flowselector_last_ret;        //定义了一个 19 位宽的寄存器，用于存储流选择器的最后一次返回值
    register<bit<4>>(MAX_NB_PREFIXES*FLOWSELECTOR_NBFLOWS) flowselector_last_ret_bin;     //定义了一个 4 位宽的寄存器，用于存储流选择器的最后一次返回值的二进制表示
    register<bit<1>>(MAX_NB_PREFIXES*FLOWSELECTOR_NBFLOWS) flowselector_correctness;      //定义了一个 1 位宽的寄存器，用于存储流选择器的正确性标志
    register<bit<2>>(MAX_NB_PREFIXES*FLOWSELECTOR_NBFLOWS) flowselector_fwloops;          //定义了一个 2 位宽的寄存器，用于存储流选择器的前向循环次数

    /** Registers used by the sliding window **/
    register<bit<6>>(MAX_NB_PREFIXES*(bit<32>)(SW_NB_BINS)) sw;                           //定义了一个 6 位宽的寄存器，用于存储滑动窗口的数据
    register<bit<19>>(MAX_NB_PREFIXES) sw_time;                                           //定义了一个 19 位宽的寄存器，用于存储滑动窗口的时间戳
    register<bit<4>>(MAX_NB_PREFIXES) sw_index;                                           //定义了一个 4 位宽的寄存器，用于存储滑动窗口的索引
    register<bit<6>>(MAX_NB_PREFIXES) sw_sum;                                             //定义了一个 6 位宽的寄存器，用于存储滑动窗口的总和

    // Register to store the threshold for each prefix (by default all the prefixes
    // have the same threshold, so this could just be a macro)
    register<bit<6>>(MAX_NB_PREFIXES) threshold_registers;                                //定义了一个 6 位宽的寄存器，用于存储每个前缀的阈值

    // List of next-hops for each prefix
    register<bit<32>>(MAX_NB_PREFIXES*3) next_hops_port;                                  //定义了一个 32 位宽的寄存器，用于存储每个前缀的下一跳端口

    // Register used to indicate whether a next-hop is working or not.
    register<bit<1>>(MAX_NB_PREFIXES) nh_avaibility_1;                                    //定义了一个 1 位宽的寄存器，用于存储下一跳 1 的可用性
    register<bit<1>>(MAX_NB_PREFIXES) nh_avaibility_2;                                    //定义了一个 1 位宽的寄存器，用于存储下一跳 2 的可用性
    register<bit<1>>(MAX_NB_PREFIXES) nh_avaibility_3;                                    //定义了一个 1 位宽的寄存器，用于存储下一跳 3 的可用性 

    // Register use to keep track for each flow, the number of flows that restart
    // after the rerouting. One per backup next-hop
    register<bit<6>>(MAX_NB_PREFIXES) nbflows_progressing_2;                             //定义了一个 6 位宽的寄存器，用于存储在重路由后重新启动的流的数量（针对备份下一跳 2）
    register<bit<6>>(MAX_NB_PREFIXES) nbflows_progressing_3;                             //定义了一个 6 位宽的寄存器，用于存储在重路由后重新启动的流的数量（针对备份下一跳 3）

    // Timestamp of the rerouting
    register<bit<19>>(MAX_NB_PREFIXES) rerouting_ts;                                     //定义了一个 19 位宽的寄存器，用于存储重路由的时间戳

    // Every time period seconds (define by MAX_FLOWS_SELECTION_TIME), the
    // controller updates this register
    register<bit<48>>(32w1) timestamp_reference;                                         //定义了一个 48 位宽的寄存器，用于存储时间戳参考值

    // Switch IP used to reply to the traceroutes
    register<bit<32>>(32w1) switch_ip;                                                   //定义了一个 32 位宽的寄存器，用于存储交换机的 IP 地址  

    bit<9> ts_second;                          //定义了一个 9 位宽的变量，用于存储时间戳的秒部分

    bit<48> ts_tmp;                            //定义了一个 48 位宽的变量，用于临时存储时间戳
    bit<6> sum_tmp;                            //定义了一个 6 位宽的变量，用于临时存储总和
    bit<6> threshold_tmp;                      //定义了一个 6 位宽的变量，用于临时存储阈值
    bit<6> correctness_tmp;                    //定义了一个 6 位宽的变量，用于临时存储正确性标志
    bit<19> rerouting_ts_tmp;                  //定义了一个 19 位宽的变量，用于临时存储重路由时间戳
    bit<2> flowselector_fwloops_tmp;           //定义了一个 2 位宽的变量，用于临时存储流选择器的前向循环次数
    bit<1> nh_avaibility_1_tmp;                //定义了一个 1 位宽的变量，用于临时存储下一跳 1 的可用性
    bit<1> nh_avaibility_2_tmp;                //定义了一个 1 位宽的变量，用于临时存储下一跳 2 的可用性
    bit<1> nh_avaibility_3_tmp;                //定义了一个 1 位宽的变量，用于临时存储下一跳 3 的可用性

    flowselector() fc;                                  //实例化了一个流选择器对象

    /**
    * Mark packet to drop
    */
    action _drop() {                                   //定义了一个名为 _drop 的动作，用于标记数据包为丢弃
        mark_to_drop();
    }

    /**
    * Set the metadata used in the normal pipeline    定义了一个名为 set_meta 的动作，用于设置元数据
    */
    action set_meta(bit<32> id, bit<1> use_blink, bit<32> default_nexthop_port) { 
        custom_metadata.id = id;
        custom_metadata.use_blink = use_blink;
        custom_metadata.next_hop_port = default_nexthop_port;
    }
    // 定义表：根据IPv4目的地址和BGP下一跳类型匹配数据包，并执行相应的动作
    table meta_fwtable {                               //定义了一个名为 meta_fwtable 的表，用于匹配数据包并执行相应的动作
        actions = {
            set_meta;
            _drop;
        }
        key = {
            pp.ipv4.dstAddr: lpm;
            custom_metadata.bgp_ngh_type: exact;
        }
        size = 20000;
        default_action = _drop;
    }


    /**
    * Set the metadata about BGP (provider, peer or customer)
    */
    action set_bgp_tag(bit<2> neighbor_bgp_type) {     //定义了一个名为 set_bgp_tag 的动作，用于设置 BGP 标签
        custom_metadata.bgp_ngh_type = neighbor_bgp_type;
    }

    table bgp_tag {                                    //定义了一个名为 bgp_tag 的表，用于匹配数据包并执行相应的动作
        actions = {
            set_bgp_tag;
            NoAction;
        }
        key = {
            standard_metadata.ingress_port: exact;
            pp.ethernet.srcAddr: exact;
        }
        size = 20000;
        default_action = NoAction; // By default bgp_ngh_type will be 0, meaning customer (used for the host)
    }

    /**
    * Set output port and destination MAC address based on port ID
    */
    action set_nh(bit<9> port, EthernetAddress smac, EthernetAddress dmac) {   //定义了一个名为 set_nh 的动作，用于设置下一跳（next hop）信息
        standard_metadata.egress_spec = port;
        pp.ethernet.srcAddr = smac;
        pp.ethernet.dstAddr = dmac;

        // Decrement the TTL by one
        pp.ipv4.ttl = pp.ipv4.ttl - 1;
    }

    table send {                                      //定义了一个名为 send 的表，用于匹配数据包并执行相应的动作
        actions = {                                   //定义了这个表可以执行的两个动作：set_nh 和 _drop。
            set_nh;
            _drop;
        }
        key = {                                       //定义表的匹配键，包括标准元数据的入口端口和自定义元数据的下一跳端口。
            custom_metadata.next_hop_port: exact;     //在这个例子中，匹配键是 custom_metadata.next_hop_port，它是一个自定义的元数据字段，用于存储下一跳端口的信息。
        }                                             //exact 表示精确匹配，即只有当数据包的下一跳端口与表中的值完全相同时，才会执行相应的动作。
        size = 1024;                                  //表的大小为 1024，即表中可以存储的最大条目数。  
        default_action = _drop;                       //默认动作是丢弃包
    }

    apply{         //应用块，用于执行一系列操作，包括读取时间戳参考值、更新时间戳参考值、设置元数据、处理 traceroute 逻辑等
        timestamp_reference.read(ts_tmp, 32w0);         // 从时间戳参考值中读取时间戳，并将其存储在 ts_tmp 变量中。32w0 表示从时间戳参考值的第 0 个位置开始读取。

        // If the difference between the reference timestamp and the current
        // timestamp is above MAX_FLOWS_SELECTION_TIME, then reference timestamp
        // is updated
        if (standard_metadata.ingress_global_timestamp - ts_tmp > MAX_FLOWS_SELECTION_TIME)
        {
            timestamp_reference.write(32w0, standard_metadata.ingress_global_timestamp);
        }//更新时间戳参考值。if语句中条件为真说明时间戳参考值与当前时间戳的差值超过了 MAX_FLOWS_SELECTION_TIME，意义是时间戳参考值已经过时，会造成流选择器的重选。
        //更新参考值的作用是为了确保流选择器能够在时间上保持同步，避免因为时间戳的差异而导致流选择器的错误选择。
        timestamp_reference.read(ts_tmp, 32w0);//从时间戳参考值中再次读取时间戳，并将其存储在 ts_tmp 变量中。这一步是为了更新时间戳参考值。

        custom_metadata.ingress_timestamp_second =
            (bit<9>)((standard_metadata.ingress_global_timestamp - ts_tmp) >> 20);
        custom_metadata.ingress_timestamp_millisecond =
            (bit<19>)((standard_metadata.ingress_global_timestamp - ts_tmp) >> 10);
        //将当前的全局时间戳与读取的时间戳参考值进行比较，计算出一个相对时间值，并将其分别存储在custom_metadata.ingress_timestamp_second和custom_metadata.ingress_timestamp_millisecond这两个自定义元数据字段中。这两个字段用于记录数据包进入设备的时间戳信息。两个字段分别的作用是将时间戳值拆分为秒和毫秒两个部分，以便更方便地处理和分析。
        bgp_tag.apply();//调用bgp_tag表的apply方法，用于处理 BGP 相关的元数据。
        meta_fwtable.apply();//调用meta_fwtable表的apply方法，用于处理元数据转发表。

        //Traceroute Logic (only for TCP probes) 
        if (pp.ipv4.isValid() && pp.tcp.isValid() && pp.ipv4.ttl == 1){//如果数据包是 IPv4 协议，并且是 TCP 协议，并且 TTL 值为 1（ICMP相关），说明这是一个 traceroute 探测包。

            // Set new headers valid 
            pp.ipv4_icmp.setValid(); //设置pp.ipv4_icmp字段为有效。这是为了确保在后续的处理中可以使用该字段。
            pp.icmp.setValid();  //设置pp.icmp字段为有效。这是为了确保在后续的处理中可以使用该字段。
            
            // Set egress port == ingress port
            standard_metadata.egress_spec = standard_metadata.ingress_port;  //icmp包的构建，将数据包的出端口（egress port）设置为入端口（ingress port）

            //Ethernet: Swap map addresses  在以太网报头中交换源地址和目的地址。
            bit<48> tmp_mac = pp.ethernet.srcAddr;
            pp.ethernet.srcAddr = pp.ethernet.dstAddr;
            pp.ethernet.dstAddr = tmp_mac;

            //Building new Ipv4 header for the ICMP packet
            //Copy original header (for simplicity)
            pp.ipv4_icmp = pp.ipv4;  //将 IPv4 头部信息复制到 IPv4 ICMP 头部，ICMP 响应数据包需要包含原始 IPv4 数据包的头部信息，以便接收方能够识别原始数据包的发送者和其他相关信息
            //Set destination address as traceroute originator
            pp.ipv4_icmp.dstAddr = pp.ipv4.srcAddr;  //这行代码将 IPv4 ICMP 头部的目的地址设置为原始 IPv4 数据包的源地址
            //Set src IP to the IP assigned to the switch
            switch_ip.read(pp.ipv4_icmp.srcAddr, 0);  //从 switch_ip 寄存器中读取交换机的 IP 地址，并将其赋值给 IPv4 ICMP 头部的源地址字段

            //Set protocol to ICMP
            pp.ipv4_icmp.protocol = IP_ICMP_PROTO;
            //Set default TTL
            pp.ipv4_icmp.ttl = 64;
            //And IP Length to 56 bytes (normal IP header + ICMP + 8 bytes of data)
            pp.ipv4_icmp.totalLen= 56;

            //Create ICMP header with
            pp.icmp.type = ICMP_TTL_EXPIRED;  //将 ICMP 头部的类型字段设置为 TTL 过期
            pp.icmp.code = 0;  //将 ICMP 头部的代码字段设置为 0

            //make sure all the packets are length 70.. so wireshark does not complain when tpc options,etc
            truncate((bit<32>)70);
        }
        else
        {
            // Get the threshold to use for fast rerouting (default is 32 flows)
            threshold_registers.read(threshold_tmp, custom_metadata.id);

            // If it is a TCP packet and destined to a destination that has Blink activated
            if (pp.tcp.isValid() && custom_metadata.use_blink == 1w1)  //检查是否是一个TCP数据包，use_blink=1w1意思是该目的地址使用了Blink协议
            {
                // If it is a SYN packet, then we set the tcp_payload_len to 1
                // (even if the packet actually does not have any payload)
                if (pp.tcp.syn == 1w1 || pp.tcp.fin == 1w1)  //如果是syn包或是fin包，那么设置tcp_payload_len为1，即使数据包实际上没有任何负载
                    custom_metadata.tcp_payload_len = 16w1;  //这里16w1意思是16位表示的1字节
                else//如果不是SYN包或FIN包，计算 TCP 负载的实际长度
                    custom_metadata.tcp_payload_len = pp.ipv4.totalLen - (bit<16>)(pp.ipv4.ihl)*16w4 - (bit<16>)(pp.tcp.dataOffset)*16w4; // ip_len - ip_hdr_len - tcp_hdr_len
                    //custom_metadata.tcp_payload_len 被设置为 pp.ipv4.totalLen 减去 IP 头部长度（pp.ipv4.ihl 乘以 16 再乘以 4），再减去 TCP 头部长度（pp.tcp.dataOffset 乘以 16 再乘以 4）
                if (custom_metadata.tcp_payload_len > 0) //如果TCP包的载荷长度大于0
                {
                    fc.apply(pp, custom_metadata, standard_metadata,  //还没整明白这个fc.apply是个干啥的函数，可能是流量控制用的
                        flowselector_key, flowselector_nep, flowselector_ts,
                        flowselector_last_ret, flowselector_last_ret_bin,
                        flowselector_correctness, flowselector_fwloops,
                        sw, sw_time, sw_index, sw_sum,
                        nbflows_progressing_2,
                        nbflows_progressing_3,
                        rerouting_ts);
                    //读取交换机状态寄存器和下一跳可用性寄存器的值
                    sw_sum.read(sum_tmp, custom_metadata.id);
                    nh_avaibility_1.read(nh_avaibility_1_tmp, custom_metadata.id);//nh_avaibility_1_tmp 是一个 1 位宽的变量，用于存储下一跳 1 的可用性
                    //快速重路由触发
                    // Trigger the fast reroute if sum_tmp is greater than the
                    // threshold (i.e., default 31)
                    if (sum_tmp > threshold_tmp && nh_avaibility_1_tmp == 0)
                    {
                        // Write 1, to deactivate this next-hop
                        // and start using the backup ones
                        nh_avaibility_1.write(custom_metadata.id, 1); //禁用当前下一跳，开始使用备份下一跳

                        // Initialize the registers used to check flow progression 初始化跟踪流进程的寄存器
                        nbflows_progressing_2.write(custom_metadata.id, 6w0);
                        nbflows_progressing_3.write(custom_metadata.id, 6w0);

                        // Storing the timestamp of the rerouting 记录重路由开始的时间
                        rerouting_ts.write(custom_metadata.id, custom_metadata.ingress_timestamp_millisecond);
                    }
                }
            }

            if (custom_metadata.use_blink == 1w1)//如果开启了Blink功能
            {//获取下一跳的可用性和重路由时间戳
                nh_avaibility_1.read(nh_avaibility_1_tmp, custom_metadata.id);
                nh_avaibility_2.read(nh_avaibility_2_tmp, custom_metadata.id);
                nh_avaibility_3.read(nh_avaibility_3_tmp, custom_metadata.id);
                rerouting_ts.read(rerouting_ts_tmp, custom_metadata.id);

                // All the selected flows, within the first second after the rerouting.
                if (custom_metadata.selected == 1w1 && rerouting_ts_tmp > 0 &&
                    (custom_metadata.ingress_timestamp_millisecond -
                    rerouting_ts_tmp) < ((bit<19>)TIMEOUT_PROGRESSION))
                {
                    // Monitoring the first backup NH 检查第一个备份路由
                    if (custom_metadata.flowselector_cellid < (FLOWSELECTOR_NBFLOWS >> 1))
                    {
                        // If the backup next-hop is working so far
                        if (nh_avaibility_2_tmp == 1w0)  //判断备份路由2的可用性
                        {//如果可用，读取下一跳的端口信息
                            next_hops_port.read(custom_metadata.next_hop_port, (custom_metadata.id*3)+1);
                            //根据不同的条件选择合适的下一跳端口，并将其存储在 custom_metadata.next_hop_port 中。
                            if (custom_metadata.is_retransmission == 1w1) // If this is a retransmission 
                            {//如果是重传数据包
                                flowselector_fwloops.read(flowselector_fwloops_tmp,
                                    (FLOWSELECTOR_NBFLOWS * custom_metadata.id) + custom_metadata.flowselector_cellid);
                                //环路检测
                                // If a forwarding loop is detected for this flow
                                if (flowselector_fwloops_tmp == FWLOOPS_TRIGGER)
                                {//如果检测到当前路由为环路，切换到备份下一跳nh
                                    // We switch to the third backup nexthop
                                    nh_avaibility_2.write(custom_metadata.id, 1);
                                    nh_avaibility_2_tmp = 1w1;
                                }
                                else//如果没有检测到转发环路
                                {//更新flowselector_fwloops的值，用于跟踪流的转发环路次数，每次检测到重传数据包时，环路次数加 1
                                    flowselector_fwloops.write((FLOWSELECTOR_NBFLOWS * custom_metadata.id)
                                    + custom_metadata.flowselector_cellid, flowselector_fwloops_tmp + 1);
                                }
                            }
                        }
                        else
                        {
                            if (nh_avaibility_3_tmp == 1w0) //备份路由3的可用性
                            {
                                // Retrieve the port ID to use for that prefix //获取要使用的端口ID
                                next_hops_port.read(custom_metadata.next_hop_port, (custom_metadata.id*3)+2);
                            }
                            else
                            {
                                next_hops_port.read(custom_metadata.next_hop_port, (custom_metadata.id*3)+0);
                            }
                        }

                    }
                    // Monitoring the second backup NH
                    else
                    {
                        // If the backup next-hop is working so far
                        if (nh_avaibility_3_tmp == 1w0)  //第三个ei
                        {
                            next_hops_port.read(custom_metadata.next_hop_port, (custom_metadata.id*3)+2);

                            if (custom_metadata.is_retransmission == 1w1) // If this is a retransmission
                            {
                                flowselector_fwloops.read(flowselector_fwloops_tmp,
                                    (FLOWSELECTOR_NBFLOWS * custom_metadata.id) + custom_metadata.flowselector_cellid);

                                // If a forwarding loop is detected for this flow
                                if (flowselector_fwloops_tmp == FWLOOPS_TRIGGER)
                                {
                                    // We switch to the third backup nexthop
                                    nh_avaibility_3.write(custom_metadata.id, 1);
                                    nh_avaibility_3_tmp = 1w1;
                                }
                                else
                                {
                                    flowselector_fwloops.write((FLOWSELECTOR_NBFLOWS * custom_metadata.id)
                                    + custom_metadata.flowselector_cellid, flowselector_fwloops_tmp + 1);
                                }
                            }
                        }
                        else
                        {
                            if (nh_avaibility_2_tmp == 1w0)
                            {
                                // Retrieve the port ID to use for that prefix
                                next_hops_port.read(custom_metadata.next_hop_port, (custom_metadata.id*3)+1);
                            }
                            else
                            {
                                next_hops_port.read(custom_metadata.next_hop_port, (custom_metadata.id*3)+0);
                            }
                        }
                    }
                }
                // Else: All the flows of the prefixes monitored by Blink 没有可用的重路由的预设操作
                else
                {
                    if (nh_avaibility_1_tmp == 1w0)
                    {
                        // Retrieve the port ID to use for that prefix
                        next_hops_port.read(custom_metadata.next_hop_port, (custom_metadata.id*3)+0);
                    }
                    else if (nh_avaibility_2_tmp == 1w0)
                    {
                        next_hops_port.read(custom_metadata.next_hop_port, (custom_metadata.id*3)+1);
                    }
                    else if (nh_avaibility_3_tmp == 1w0)
                    {
                        next_hops_port.read(custom_metadata.next_hop_port, (custom_metadata.id*3)+2);
                    }
                    else
                    {
                        // If none of the backup next-hop is working, then we use primary next-hop
                        next_hops_port.read(custom_metadata.next_hop_port, (custom_metadata.id*3)+0);
                    }
                }

                // Check if after one second at least more than half of the flows have
                // restarted otherwise deactive the corresponding next-hop
                if (rerouting_ts_tmp > 0 && (custom_metadata.ingress_timestamp_millisecond -
                    rerouting_ts_tmp) > ((bit<19>)TIMEOUT_PROGRESSION))
                {
                    nbflows_progressing_2.read(correctness_tmp, custom_metadata.id);
                    if (correctness_tmp < MIN_NB_PROGRESSING_FLOWS && nh_avaibility_2_tmp == 0)
                    {
                        nh_avaibility_2.write(custom_metadata.id, 1);
                    }

                    nbflows_progressing_3.read(correctness_tmp, custom_metadata.id);
                    if (correctness_tmp < MIN_NB_PROGRESSING_FLOWS && nh_avaibility_3_tmp == 0)
                    {
                        nh_avaibility_3.write(custom_metadata.id, 1);
                    }
                }
            }

            send.apply();
        }
    }
}


/* ------------------------------------------------------------------------- */
control egress(inout Parsed_packet pp,
               inout custom_metadata_t custom_metadata,
               inout standard_metadata_t standard_metadata) {

   apply { }
}

/* ------------------------------------------------------------------------- */
control verifyChecksum(inout Parsed_packet pp, inout custom_metadata_t meta) {
    apply {
    }
}

/* ------------------------------------------------------------------------- */
control computeChecksum(inout Parsed_packet pp, inout custom_metadata_t meta) {//计算校验和
    apply {
    	update_checksum(
    	    pp.ipv4.isValid(),
                { pp.ipv4.version,
    	          pp.ipv4.ihl,
                  pp.ipv4.dscp,
                  pp.ipv4.ecn,
                  pp.ipv4.totalLen,
                  pp.ipv4.identification,
                  pp.ipv4.flags,
                  pp.ipv4.fragOffset,
                  pp.ipv4.ttl,
                  pp.ipv4.protocol,
                  pp.ipv4.srcAddr,
                  pp.ipv4.dstAddr },
                  pp.ipv4.hdrChecksum,
                  HashAlgorithm.csum16);

        update_checksum(
        pp.ipv4_icmp.isValid(),
            { pp.ipv4_icmp.version,
              pp.ipv4_icmp.ihl,
              pp.ipv4_icmp.dscp,
              pp.ipv4_icmp.ecn,
              pp.ipv4_icmp.totalLen,
              pp.ipv4_icmp.identification,
              pp.ipv4_icmp.flags,
              pp.ipv4_icmp.fragOffset,
              pp.ipv4_icmp.ttl,
              pp.ipv4_icmp.protocol,
              pp.ipv4_icmp.srcAddr,
              pp.ipv4_icmp.dstAddr },
              pp.ipv4_icmp.hdrChecksum,
              HashAlgorithm.csum16);

        update_checksum(
        pp.icmp.isValid(),
            { pp.icmp.type,
              pp.icmp.code,
              pp.icmp.unused,
              pp.ipv4.version,
	          pp.ipv4.ihl,
              pp.ipv4.dscp,
              pp.ipv4.ecn,
              pp.ipv4.totalLen,
              pp.ipv4.identification,
              pp.ipv4.flags,
              pp.ipv4.fragOffset,
              pp.ipv4.ttl,
              pp.ipv4.protocol,
              pp.ipv4.hdrChecksum,
              pp.ipv4.srcAddr,
              pp.ipv4.dstAddr,
              pp.tcp.srcPort,
              pp.tcp.dstPort,
              pp.tcp.seqNo
              },
              pp.icmp.checksum,
              HashAlgorithm.csum16);
        }
}

/* ------------------------------------------------------------------------- */
control DeparserImpl(packet_out packet, in Parsed_packet pp) {  //将解析提取出来的数据重新组合成数据包
    apply {
        packet.emit(pp.ethernet);
        packet.emit(pp.ipv4_icmp);
        packet.emit(pp.icmp);
        packet.emit(pp.ipv4);
        packet.emit(pp.tcp);
    }
}

V1Switch(ParserImpl(),
    verifyChecksum(),
    ingress(),
    egress(),
    computeChecksum(),
    DeparserImpl()) main;
